/*
  Copyright (c) 2002, 2003 Russell Morris
  All rights reserved.

  Redistribution and use in source and binary forms, with or without modification, 
  are permitted provided that the following conditions are met:

    * Redistributions of source code must retain the above copyright notice, 
      this list of conditions and the following disclaimer. 
      
    * Redistributions in binary form must reproduce the above copyright notice, 
      this list of conditions and the following disclaimer in the documentation 
      and/or other materials provided with the distribution. 
      
    * The names of the contributors may not be used to endorse or promote 
      products derived from this software without specific prior written permission. 
      
    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
    AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
    IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
    ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 
    LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
    CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
    SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
    INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER 
    IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
    ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 
    THE POSSIBILITY OF SUCH DAMAGE.
*/

using System;
using System.Collections;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.ComponentModel.Design.Serialization;
using System.Windows.Forms;


namespace WRM.Windows.Forms
{
    /// <summary>
    /// A class the represents a collection of controls that is displayed
    /// by a <see cref="PropertyTree">PropertyTree</see>.
    /// </summary>
    [Designer(typeof(WRM.Windows.Forms.Design.PropertyPaneDesigner))]
    [Designer(typeof(WRM.Windows.Forms.Design.PropertyPaneRootDesigner),typeof(IRootDesigner))]
    [DesignerSerializer(typeof(WRM.Windows.Forms.Design.PaneNodeCollectionSerializer),
         typeof(CodeDomSerializer))]
    [DesignTimeVisible(true)]
    [ToolboxItem(true)]
    public class PropertyPane : UserControl, ISupportInitialize
    {
        /// <summary>
        ///   The PropertyTree that owns us
        /// </summary>
        private PropertyTree mPropTree;

        /// <summary>
        ///   Our PaneNode property
        /// </summary>
        private PaneNode mPaneNode;

        /// <summary>
        ///   Our stash to be used when mPaneNode isn't set.
        /// </summary>
        private PaneNodeCollection mStash;

        /// <summary>
        ///   Nodes adding during InitializeComponent
        /// </summary>
        private ArrayList mAddedDuringInit;

        /// <summary>
        ///   Boolean variable indicating whether or not we are being initialized
        ///   by designer-generated code.
        /// </summary>
        private bool mInit = false;
    
        /// <summary>
        ///   The default constructor
        /// </summary>
        public PropertyPane()
        {
            mStash = new PaneNodeCollection(new PaneNode());
            mAddedDuringInit = new ArrayList();
        }

        internal event PaneTabOutHandler TabOut;
      
        /// <summary>
        ///   Gets the <see cref="PropertyTree"/> that owns this <c>PropertyPane</c>, 
        ///   if any.
        /// </summary>
        /// <value>
        /// The <see cref="PropertyTree"/> that owns this <c>PropertyPane</c>, 
        /// if any
        /// </value>
        [Browsable(false)]
        internal PropertyTree Owner
        {
            get
            {
                return mPropTree;
            }
            set
            {
                mPropTree = value;
            }			
        }
    
        /// <summary>
        ///   Gets or sets the <see cref="PaneNode"/> that represents
        ///   this <c>PropertyPane</c>, if any.
        /// </summary>
        /// <value>
        /// The <see cref="PaneNode"/> that represents this <c>PropertyPane</c>, 
        /// if any.
        /// </value>
        internal PaneNode PaneNodeInternal
        {
            get
            {
                return mPaneNode;
            }
            set
            {
                PaneNode oldVal = mPaneNode;
                mPaneNode = value;

                if(oldVal != null && mPaneNode == null)
                    oldVal.PaneNodes.CopyTo(mStash);
                else if((oldVal == null || oldVal.IsDummy) &&
                    mPaneNode != null && 
                    !(this is SharedPropertyPane))
                {
                    foreach(PaneNode childNode in mStash)
                        mPaneNode.PaneNodes.Add(childNode);
                }
            }
        }

        /// <summary>
        /// Gets the <see cref="PaneNode"/> associated with this <c>PropertyPane</c>,
        /// if any
        /// </summary>
        /// <value>
        /// The <see cref="PaneNode"/> associated with this <c>PropertyPane</c>, if
        /// any
        /// </value>
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public PaneNode PaneNode
        {
            get
            {
                return mPaneNode;
            }
        }

        /// <summary>
        ///   Gets the <see cref="PaneNodeCollection"/> that holds this child
        ///   <see cref="PaneNode"/>s <c>PropertyPane</c>.
        /// </summary>
        /// <value>
        ///   <para>
        ///   If <see cref="PropertyPane.PaneNode"/> is non-<c>null</c>, then
        ///   this property returns the value of <see cref="PropertyPane.PaneNode.PaneNodes"/>.
        ///   </para>
        ///   <para>
        ///   If, however, <see cref="PropertyPane.PaneNode"/> is <c>null</c>, this property returns
        ///   an internally held <c>PaneNodeCollection</c> that is used until this
        ///   <c>PropertyPane</c> is hooked up to a real <see cref="PaneNode"/>.
        ///   </para>
        /// </value>
        /// <remarks>
        /// In the event that this <c>PropertyPane</c> has not yet been hooked up to a 
        /// real <see cref="PaneNode"/>, the value returned from this property is an 
        /// internally held temporary <c>PaneNodeCollection</c>.  When this <c>PropertyPane</c>
        /// is attached to a real <see cref="PaneNode"/>, the members of this
        /// <c>PaneNodeCollection</c> are copied over to it.
        /// </remarks>
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
        public PaneNodeCollection PaneNodes
        {
            get
            {
                if(mPaneNode == null)
                    return mStash;
                else
                    return mPaneNode.PaneNodes;
            }
        }

        /// <summary>
        ///   Function called when this <c>PropertyPane</c> is about to be activated
        /// </summary>
        /// <param name="sender">
        /// The <see cref="PropertyTree"/> that sent the event</param>
        /// <param name="psea">Info about the selection process</param>
        /// <remarks>
        ///   <para>
        ///   This function is called and checked before the
        ///   <see cref="PropertyTree.PaneActivating">PropertyTree.PaneActivating</see>
        ///   event is fired.
        ///   </para>
        ///   <para>
        ///   If <paramref name="psea">psea.Cancel</paramref> is set to <c>true</c>, then the
        ///   selection process is aborted.
        ///   </para>
        /// </remarks>
        public virtual void OnPaneActivating(PropertyTree sender, PaneSelectionEventArgs psea)
        {
        }

        /// <summary>
        ///   Function called after this <c>PropertyPane</c> has been activated
        /// </summary>
        /// <param name="sender">
        /// The <see cref="PropertyTree"/> that sent the event</param>
        /// <param name="psea">Info about the selection process</param>
        /// <remarks>
        ///   <para>
        ///   This function is called and checked before the
        ///   <see cref="PropertyTree.PaneActivating">PropertyTree.PaneActivating</see>
        ///   event is fired.
        ///   </para>
        /// </remarks>
        public virtual void OnPaneActivated(PropertyTree sender, PaneSelectionEventArgs psea)
        {
        }

        /// <summary>
        ///   Function called before this <c>PropertyPane</c> is deactivated.
        /// </summary>
        /// <param name="sender">
        /// The <see cref="PropertyTree"/> that sent the event</param>
        /// <param name="psea">Info about the selection process</param>
        /// <remarks>
        ///   <para>
        ///   This function is called and checked before the
        ///   <see cref="PropertyTree.PaneActivating">PropertyTree.PaneActivating</see>
        ///   event is fired.
        ///   </para>
        ///   <para>
        ///   If <paramref name="psea">psea.Cancel</paramref> is set to <c>true</c>, then the
        ///   pane selection process is aborted.
        ///   </para>
        /// </remarks>
        public virtual void OnPaneDeactivating(PropertyTree sender, PaneSelectionEventArgs psea)
        {
        }

        /// <summary>
        ///   Function called after this <c>PropertyPane</c> has been deactivated.
        /// </summary>
        /// <param name="sender">
        /// The <see cref="PropertyTree"/> that sent the event</param>
        /// <param name="psea">Info about the selection process</param>
        /// <remarks>
        ///   <para>
        ///   This function is called and checked before the
        ///   <see cref="PropertyTree.PaneActivating">PropertyTree.PaneActivating</see>
        ///   event is fired.
        ///   </para>
        /// </remarks>
        public virtual void OnPaneDeactivated(PropertyTree sender, PaneSelectionEventArgs psea)
        {
        }


        private void InitializeComponent()
        {
            // 
            // PropertyPane
            // 
            this.Name = "PropertyPane";
            this.Size = new System.Drawing.Size(152, 152);

        }

        /// <summary>
        /// Gets or sets the text that will appear in the <see cref="PropertyTree">TreeView</see> for 
        /// this <c>PropertyPane</c>
        /// </summary>
        /// <value>
        /// The text that will appear in the <see cref="PropertyTree"/> for this
        /// <c>PropertyPane</c>
        /// </value>
        /// <remarks>
        ///   <see cref="UserControl">UserControl</see>, overrides this property and hides it
        ///   from the designer.  Overriding it again makes sure that it is browsable and that
        ///   the designer serializer knows that it should write out its value to code.
        /// </remarks>
        [Browsable(true)]
        [Category("Behavior")]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
        public override string Text
        {
            get
            {
                return base.Text;
            }
            set
            {
                base.Text = value;
                if(mPaneNode != null)
                    mPaneNode.Text = value;
            }
        }

        private int mStashImageIndex = -1;
        /// <summary>
        /// Gets or sets the index of the image in <see cref="PropertyTree.ImageList"/> that will be
        /// used to represent this <c>PropertyPane</c>'s <see cref="PropertyPane.PaneNode"/>.
        /// </summary>
        /// <value>
        /// If this <c>PropertyPane</c> has been inserted into a 
        /// <see cref="PropertyTree"/>, this value comes from 
        /// <see cref="PropertyPane.PaneNode.ImageIndex"/>.  However, if this 
        /// <c>PropertyPane</c> has not been inserted into a <see cref="PropertyTree"/>, 
        /// this value represents and internally held value that this <c>PropertyPane</c>
        /// will use once it is added to a <see cref="PropertyTree"/> and its
        /// <see cref="PropertyPane.PaneNode"/> is set to a valid value.
        /// </value>
        /// <remarks>
        /// If this <c>PropertyPane</c> is a <see cref="SharedPropertyPane"/>, setting this
        /// value will cause a change in whatever <see cref="PaneNode"/> is currently
        /// referenced by <see cref="PropertyPane.PaneNode"/>
        /// </remarks>
        [Category("Appearance")]
        public int ImageIndex
        {
            get
            {
                if(mPaneNode == null)
                    return mStashImageIndex;
                else
                    return mPaneNode.ImageIndex;
            }
            set
            {
                mStashImageIndex = value;
                if(mPaneNode != null)
                    mPaneNode.ImageIndex = value;
            }
        }

        private int mStashSelectedImageIndex = -1;
        /// <summary>
        /// Gets or sets the index of the image in <see cref="PropertyTree.ImageList"/> that will be
        /// used to represent this <c>PropertyPane</c>'s <see cref="PropertyPane.PaneNode"/>.
        /// </summary>
        /// <value>
        /// If this <c>PropertyPane</c> has been inserted into a 
        /// <see cref="PropertyTree"/>, this value comes from 
        /// <see cref="PropertyPane.PaneNode.ImageIndex"/>.  However, if this 
        /// <c>PropertyPane</c> has not been inserted into a <see cref="PropertyTree"/>, 
        /// this value represents and internally held value that this <c>PropertyPane</c>
        /// will use once it is added to a <see cref="PropertyTree"/> and its
        /// <see cref="PropertyPane.PaneNode"/> is set to a valid value.
        /// </value>
        /// <remarks>
        /// If this <c>PropertyPane</c> is a <see cref="SharedPropertyPane"/>, setting this
        /// value will cause a change in whatever <see cref="PaneNode"/> is currently
        /// referenced by <see cref="PropertyPane.PaneNode"/>
        /// </remarks>
        [Category("Appearance")]
        public int SelectedImageIndex
        {
            get
            {
                if(mPaneNode == null)
                    return mStashSelectedImageIndex;
                else
                    return mPaneNode.SelectedImageIndex;
            }
            set
            {
                mStashSelectedImageIndex = value;
                if(mPaneNode != null)
                    mPaneNode.SelectedImageIndex = value;
            }
        }

        /// <summary>
        ///   Handle the Resize event.
        /// </summary>
        /// <param name="e">Event information</param>
        /// <remarks>
        ///   This event is overridden in case our background is complex (ie 
        ///   because of Theme support) and it needs to be fully repainted
        ///   after each resize.
        /// </remarks>
        protected override void OnResize(EventArgs e)
        {
            base.OnResize(e);

            Invalidate(ClientRectangle);
        }

    #region Implementation of ISupportInitialize
        /// <summary>
        /// Implementation of <see cref="ISupportInitialize.BeginInit"/>
        /// </summary>
        public void BeginInit()
        {
            mInit = true;
        }

        /// <summary>
        /// Implementation of <see cref="ISupportInitialize.EndInit"/>
        /// </summary>
        public void EndInit()
        {
            mInit = false;
            FlushInitCache();
        }
    #endregion

        /// <summary>
        /// Called to push any newly-added <see cref="PaneNode"/>s into the 
        /// <see cref="PaneNodes"/> object.
        /// </summary>
        private void FlushInitCache()
        {
            foreach(PaneAddInfo pai in mAddedDuringInit)
                PaneNodes.Add(pai.Instance,pai.Index,pai.ImageIndex,pai.SelectedImageIndex);

            mAddedDuringInit.Clear();
        }

        /// <summary>
        /// Fires the <see cref="PropertyPane.TabOut"/> event to notify an listeners that
        /// focus should tab "out" of this <c>PropertyPane</c>.
        /// </summary>
        /// <param name="forward">Whether or not the tabbing is moving forward</param>
        /// <returns>
        /// Returns <c>true</c> if a listener handled this event - <c>false</c> otherwise.
        /// </returns>
        private bool OnTabOut(bool forward)
        {
            PaneTabOutEventArgs e = new PaneTabOutEventArgs(forward);

            if(TabOut != null)
                TabOut(PaneNode,e);

            return e.Success;
        }

        /// <summary>
        /// Handles processing of the Tab key
        /// </summary>
        /// <param name="forward">Whether or not the tabbing is moving forward</param>
        /// <returns>
        /// Returns <c>true</c> if this class handled the event, <c>false</c> otherwise
        /// </returns>
        /// <remarks>
        /// This is overridden so that the <see cref="PropertyPane.OnTabOut"/> event can
        /// be fired, if necessary.
        /// </remarks>
        protected override bool ProcessTabKey(bool forward)
        {
            bool success = SelectNextControl(ActiveControl,forward,true,true,false);
            if(!success)
                return OnTabOut(forward);
            else
                return success;
        }

        /// <summary>
        ///   Information about a <c>PropertyPane</c> that was added as a child.
        /// </summary>
        /// <remarks>
        /// This struct is used to cache the calls to <see cref="PaneNodes.Add"/> between
        /// the <see cref="ISupportInitialize.BeginInit"/> and 
        /// <see cref="ISupportInitialize.EndInit"/> calls.
        /// </remarks>
        private struct PaneAddInfo
        {
            public PropertyPane Instance;
            public int Index;
            public int ImageIndex;
            public int SelectedImageIndex;

            public PaneAddInfo(
                PropertyPane pp,
                int index,
                int imageIndex,
                int selectedImageIndex)
            {
                Instance = pp;
                Index = index;
                ImageIndex = imageIndex;
                SelectedImageIndex = selectedImageIndex;
            }
        }
    }

    /// <summary>
    /// Class representing arguments involved when the Tab key 
    /// is used to tab out of a <see cref="PropertyPane"/> 
    /// </summary>
    public class PaneTabOutEventArgs : EventArgs
    {
        private bool mForward;
        private bool mSuccess;

        /// <summary>
        /// Creates a new isntance of <c>PaneTabOutEventArgs</c>
        /// </summary>
        /// <param name="forward">
        /// Whether or not the tab is moving forward
        /// </param>
        internal PaneTabOutEventArgs(bool forward)
        {
            mForward = forward;
            mSuccess = false;
        }

        /// <summary>
        /// Gets a boolean value indicating whether or not the tab is 
        /// moving forward
        /// </summary>
        /// <value>
        /// <c>true</c> if the tab is moving forward, <c>false</c> if it
        /// is moving backward (i.e. <c>Shift + Tab</c>)
        /// </value>
        public bool Forward
        {
            get
            {
                return mForward;
            }
        }

        /// <summary>
        /// Gets or sets a boolean value indicating whether or not the 
        /// listener consumed this event.
        /// </summary>
        /// <value>
        /// <c>true</c> to indicate that the handler of this event 
        /// consumed the event and that no further action is to be
        /// taken by the <see cref="PropertyPane"/>.  <c>false</c> to allow
        /// the Tab to be processed normally.
        /// </value>
        public bool Success
        {
            get
            {
                return mSuccess;
            }
            set
            {
                mSuccess |= value;
            }
        }
    }

    /// <summary>
    /// A delegate to handle the event fired when a <see cref="PropertyPane"/>
    /// is tabbed out of.
    /// </summary>
    /// <param name="sender">
    /// The sender of this event
    /// </param>
    /// <param name="e">
    /// Information about the event
    /// </param>
    public delegate void PaneTabOutHandler(object sender, PaneTabOutEventArgs e);
}
